/* -*-c++-*- IfcGears - www.ifcgears.com  - Copyright (C) 2011 Fabian Gerold
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 * OpenSceneGraph Public License for more details.
*/

#include <osg/Group>
#include <osg/Geode>
#include <osg/Geometry>
#include <osg/ShapeDrawable>
#include <osg/LineWidth>
#include <osg/Point>
#include <osgUtil/Tessellator>
#include <osgUtil/Optimizer>

#include "carve/input.hpp"
#include "ifcgears/IFC2X4/include/IfcSectionedSpine.h"
#include "ifcgears/IFC2X4/include/IfcReferencedSectionedSpine.h"
#include "ifcgears/IFC2X4/include/IfcReferenceCurve.h"
#include "ifcgears/IFC2X4/include/IfcReferenceCurve3D.h"
#include "ifcgears/IFC2X4/include/IfcCompositeCurve.h"
#include "ifcgears/IFC2X4/include/IfcRationalBSplineCurveWithKnots.h"
#include "ifcgears/IFC2X4/include/IfcProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcReferencePlacement.h"

#include "ifcgears/IFC2X4/include/IfcCartesianPoint.h"
#include "ifcgears/IFC2X4/include/IfcReferenceCurvePlacement.h"
#include "ifcgears/IFC2X4/include/IfcArbitraryClosedProfileDef.h"
#include "ifcgears/IFC2X4/include/IfcPolyline.h"
#include "ifcgears/IFC2X4/include/IfcParameterValue.h"
#include "ifcgears/IFC2X4/include/IfcCartesianPoint.h"
#include "ifcgears/IFC2X4/include/IfcLengthMeasure.h"
#include "ifcgears/IFC2X4/include/IfcDirection.h"
#include "ifcgears/IFC2X4/include/IfcArbitraryProfileDefWithVoids.h"
#include "ifcgears/IFC2X4/include/IfcNurbsProfile.h"
#include "ifcgears/IFC2X4/include/IfcRationalBSplineSurfaceWithKnots.h"

#include "ifcgears/model/IfcGearsModel.h"
#include "ifcgears/model/UnitConverter.h"
#include "ifcgears/model/IfcGearsException.h"
#include "ifcgears/model/shared_ptr.h"
#include "ProfileConverter.h"
#include "RepresentationConverter.h"

void RepresentationConverter::convertIfcSectionedSpine( const shared_ptr<IfcSectionedSpine>& spine, const carve::math::Matrix& pos, shared_ptr<ItemData> item_data )
{
	const shared_ptr<IfcCompositeCurve> spine_curve = spine->m_SpineCurve;
	const std::vector<shared_ptr<IfcProfileDef> >& vec_cross_sections = spine->m_CrossSections;
	const std::vector<shared_ptr<IfcAxis2Placement3D> >& vec_cross_section_positions = spine->m_CrossSectionPositions;

	std::vector<shared_ptr<IfcProfileDef> >::iterator it_cross_sections;

	unsigned int num_cross_sections = vec_cross_sections.size();
	if( vec_cross_section_positions.size() < num_cross_sections )
	{
		num_cross_sections = vec_cross_section_positions.size();
	}

	std::vector<shared_ptr<IfcCompositeCurveSegment> > segements = spine_curve->m_Segments;
	int num_segments = segements.size();
	if( vec_cross_section_positions.size() < num_segments+1 )
	{
		num_segments = vec_cross_section_positions.size()-1;
	}

	std::vector<carve::geom::vector<3> > curve_polygon;
	std::vector<carve::geom::vector<3> > segment_start_points;
	convertIfcCurve( spine_curve, curve_polygon, segment_start_points );

}


void RepresentationConverter::convertIfcReferencedSectionedSpine( const shared_ptr<IfcReferencedSectionedSpine>& spine, const carve::math::Matrix& pos, shared_ptr<ItemData> item_data )
{
	std::stringstream err;

	shared_ptr<IfcReferenceCurve> spine_curve = spine->m_SpineCurve;
	std::vector<shared_ptr<IfcProfileDef> >& vec_cross_sections_unordered = spine->m_CrossSections;
	std::vector<shared_ptr<IfcReferencePlacement> >& vec_cross_section_positions_unordered = spine->m_CrossSectionPositions;

	// copy cross sections
	std::vector<shared_ptr<IfcProfileDef> >::iterator it_cross_sections;
	std::vector<shared_ptr<IfcProfileDef> > vec_cross_sections = vec_cross_sections_unordered;

	// copy placements
	std::vector<shared_ptr<IfcReferencePlacement> >::iterator it_placements;
	std::vector<shared_ptr<IfcReferenceCurvePlacement> > vec_cross_section_positions;
	for( it_placements=vec_cross_section_positions_unordered.begin(); it_placements!=vec_cross_section_positions_unordered.end(); ++it_placements )
	{
		shared_ptr<IfcReferencePlacement> reference_placement = (*it_placements);

		shared_ptr<IfcReferenceCurvePlacement> reference_curve_placement = dynamic_pointer_cast<IfcReferenceCurvePlacement>(reference_placement);
		if( reference_curve_placement )
		{
			vec_cross_section_positions.push_back( reference_curve_placement );
		}
	}

	unsigned int num_cross_sections = vec_cross_sections.size();
	if( vec_cross_section_positions.size() < num_cross_sections )
	{
		num_cross_sections = vec_cross_section_positions.size();
	}

	shared_ptr<carve::input::PolyhedronData> polyhedron_data( new carve::input::PolyhedronData() );

	// sort placements according to abscissa
	std::vector<shared_ptr<IfcReferenceCurvePlacement> >::iterator it_curve_placements;
	std::vector<shared_ptr<IfcReferenceCurvePlacement> >::iterator it_curve_placements_inner;

	for( unsigned int i=0; i<num_cross_sections; ++i )
	{
		shared_ptr<IfcReferenceCurvePlacement> reference_curve_placement = vec_cross_section_positions[i];
		double abscissa = reference_curve_placement->m_CurvilinearAbscissa->m_value;

		for( unsigned int j=i+1; j<num_cross_sections; ++j )
		{
			shared_ptr<IfcReferenceCurvePlacement> other = vec_cross_section_positions[j];
			double abscissa_other = other->m_CurvilinearAbscissa->m_value;

			if( abscissa_other < abscissa )
			{
				// reordering necessary
				shared_ptr<IfcReferenceCurvePlacement> copy = vec_cross_section_positions[i];
				vec_cross_section_positions[i] = vec_cross_section_positions[j];
				vec_cross_section_positions[j] = copy;

				shared_ptr<IfcProfileDef> copy_profile = vec_cross_sections[i];
				vec_cross_sections[i] = vec_cross_sections[j];
				vec_cross_sections[j] = copy_profile;
				abscissa = abscissa_other;
			}
		}
	}

	if( dynamic_pointer_cast<IfcReferenceCurve3D>(spine_curve) )
	{
		shared_ptr<IfcReferenceCurve3D> spine_curve_3d = dynamic_pointer_cast<IfcReferenceCurve3D>(spine_curve);
		shared_ptr<IfcCurve> reference_curve = spine_curve_3d->m_Curve3D;

	}
}

#ifdef USE_OSG_REPRESENTATION
bool RepresentationConverter::bisectingPlane( osg::Vec3d& n, osg::Vec3d& v1, osg::Vec3d& v2, osg::Vec3d& v3)
{
	bool valid;
	osg::Vec3d v21 = v2 - v1;
	osg::Vec3d v32 = v3 - v2;
	double len21 = v21.length();
	double len32 = v32.length();
	double dot;

	if( len21 <= GEOM_TOLERANCE * len32)
	{
		if( len32 == 0.0)
		{
			// all three points lie ontop of one-another
			n.set( 0.0, 0.0, 0.0 );
			valid = false;
		}
		else
		{
			// return a normalized copy of v32 as bisector
			len32 = 1.0 / len32;
			n = v32*len32;
			valid = true;
		}

	}
	else
	{
		valid = true;
		if( len32 <= GEOM_TOLERANCE * len21)
		{
			// return a normalized copy of v21 as bisector
			len21 = 1.0 / len21;
			n = v21*len21;
		}
		else
		{
			// normalize v21 to be of unit length
			len21 = 1.0 / len21;
			v21 = v21*len21;

			// normalize v32 to be of unit length
			len32 = 1.0 / len32;
			v32 = v32*len32;

			dot = v32*v21;

			// if dot == 1 or -1, then points are colinear
			if( (dot >= (1.0-GEOM_TOLERANCE)) || (dot <= (-1.0+GEOM_TOLERANCE)))
			{
				n = -v21;
			}
			else
			{
				n = (v32 + v21)*dot - v32 - v21;
				n.normalize();
			}
		}
	}
	return valid;
}

bool RepresentationConverter::adjustControlPoints( const shared_ptr<IfcReferencedSectionedSpine>& solid, 
	std::vector<std::vector<std::vector<shared_ptr<IfcCartesianPoint> > > >& vec,
	std::vector<std::vector<std::vector<double> > >& vec_weigths )
{
	std::stringstream err;
	shared_ptr<IfcReferenceCurve> ref_curve = solid->m_SpineCurve;

	shared_ptr<IfcReferenceCurve3D> ref_curve_3d = dynamic_pointer_cast<IfcReferenceCurve3D>(ref_curve);
	if( !ref_curve_3d )
	{
		return false;
	}
	const shared_ptr<IfcRationalBSplineCurveWithKnots> nurbs_axis = dynamic_pointer_cast<IfcRationalBSplineCurveWithKnots>(ref_curve_3d->m_Curve3D);
	if( !nurbs_axis )
	{
		return false;
	}

	const std::vector<shared_ptr<IfcCartesianPoint> >& axis_control_points = nurbs_axis->m_ControlPointsList;
	const std::vector<double>& axis_weights = nurbs_axis->m_WeightsData;
	const unsigned int num_axis_points = axis_control_points.size();

	// IfcReferencedSectionedSpine:
	//shared_ptr<IfcReferenceCurve>					m_SpineCurve;
	//std::vector<shared_ptr<IfcProfileDef> >			m_CrossSections;
	//std::vector<shared_ptr<IfcReferencePlacement> >	m_CrossSectionPositions;
	if( num_axis_points < 2 )
	{
		return false;
	}
	if( solid->m_CrossSections.size() != num_axis_points )
	{
		return false;
	}

	std::vector<shared_ptr<IfcProfileDef> >& cross_sections = solid->m_CrossSections;
	shared_ptr<IfcProfileDef> first_profile = cross_sections[0];
	shared_ptr<IfcNurbsProfile> first_section_profile = dynamic_pointer_cast<IfcNurbsProfile>(first_profile);
	shared_ptr<IfcRationalBSplineSurfaceWithKnots> first_section = first_section_profile->m_Surface;
	if( !first_section )
	{
		return false;
	}

	const std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >& vec_control_points = first_section->m_ControlPointsList;
	const unsigned int num_points_xi = vec_control_points.size();
	if( num_points_xi < 2 )
	{
		return false;
	}
	const unsigned int num_points_eta = vec_control_points[0].size();
	if( num_points_eta < 2 )
	{
		return false;
	}


	const unsigned int num_points_zeta = first_section->m_ControlPointsList[0].size();
	if( num_points_zeta < 2 )
	{
		return false;
	}

	osg::Vec3d bisecting;		// bisecting plane

	vec.resize( num_axis_points );
	vec_weigths.resize( num_axis_points );

	for( unsigned int i=0; i<num_axis_points; ++i )
	{
		shared_ptr<IfcProfileDef> profile = cross_sections[i];
		shared_ptr<IfcNurbsProfile> nurbs_profile = dynamic_pointer_cast<IfcNurbsProfile>(profile);
		if( !nurbs_profile )
		{
			return false;
		}
		shared_ptr<IfcRationalBSplineSurfaceWithKnots> section = nurbs_profile->m_Surface;
		const std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >&	section_control_points = section->m_ControlPointsList;
		const std::vector<std::vector<double> >&	section_weights = section->m_WeightsData;

		if( section_control_points.size() != section_weights.size() )
		{
			throw IfcGearsException( "section_control_points.size() != section_weights.size()" );
		}

		shared_ptr<IfcCartesianPoint> point_i = axis_control_points[i];

		osg::Vec3d axis_point;
		convertIfcCartesianPoint( point_i, axis_point );

		// get the bisecting plane
		if( i == 0 )
		{
			point_i = axis_control_points[i+1];
			osg::Vec3d local_z( 0, 0, 1 );
			osg::Vec3d axis_point_next;
			convertIfcCartesianPoint( point_i, axis_point_next );

			osg::Vec3d diff = axis_point - axis_point_next;
			bisecting = diff;

		}
		else if( i == num_axis_points-1 )
		{
			point_i = axis_control_points[i-1];
			osg::Vec3d local_z( 0, 0, 1 );

			osg::Vec3d axis_point_previous;
			convertIfcCartesianPoint( point_i, axis_point_previous );
			osg::Vec3d diff = axis_point_previous - axis_point;
			bisecting = diff;
		}
		else
		{
			point_i = axis_control_points[i-1];
			osg::Vec3d bisect;
			osg::Vec3d previous;
			convertIfcCartesianPoint( point_i, previous );
			
			point_i = axis_control_points[i+1];
			osg::Vec3d next;
			convertIfcCartesianPoint( point_i, next );
			
			bisectingPlane( bisecting, previous, axis_point, next );	
		}

		osg::Quat rotation;
		rotation.makeRotate( osg::Vec3d(-1,0,0), bisecting );

		std::vector<std::vector<shared_ptr<IfcCartesianPoint> > >& vec1 = vec[i];
		std::vector<std::vector<double> >& vec_weigths1 = vec_weigths[i];
		vec1.resize( section_control_points.size() );
		vec_weigths1.resize( section_control_points.size() );
		for( unsigned int j_xi=0; j_xi<section_control_points.size(); ++j_xi )
		{
			const std::vector<shared_ptr<IfcCartesianPoint> >&	section_control_points_eta = section_control_points[j_xi];
			const std::vector<double>&	section_weights_eta = section_weights[j_xi];

			std::vector<shared_ptr<IfcCartesianPoint> >& vec2 = vec1[j_xi];
			std::vector<double>& vec_weights2 = vec_weigths1[j_xi];
			vec2.resize( section_control_points_eta.size() );
			vec_weights2.resize( section_control_points_eta.size() );
			for( unsigned int j_eta=0; j_eta<section_control_points_eta.size(); ++j_eta )
			{
				shared_ptr<IfcCartesianPoint> cp = section_control_points_eta[j_eta];
				osg::Vec3d p;
				convertIfcCartesianPoint( cp, p );
				
				osg::Vec3d p_local( 0.0, p.x(), p.y() );
				p_local = rotation*p_local;

				// translate to current axis point
				p_local += axis_point;

				shared_ptr<IfcCartesianPoint> pt( new IfcCartesianPoint() );
				pt->m_Coordinates.push_back( shared_ptr<IfcLengthMeasure>(new IfcLengthMeasure( p_local.x() ) ) );
				pt->m_Coordinates.push_back( shared_ptr<IfcLengthMeasure>(new IfcLengthMeasure( p_local.y() ) ) );
				pt->m_Coordinates.push_back( shared_ptr<IfcLengthMeasure>(new IfcLengthMeasure( p_local.z() ) ) );
				vec2[j_eta] = pt;
				vec_weights2[j_eta] = section_weights_eta[j_eta];
			}
		}
	}

	return true;
}
#endif
